package com.mtons.mblog.modules.service.impl;

import com.mtons.mblog.base.lang.Consts;
import com.mtons.mblog.base.lang.MemoryType;
import com.mtons.mblog.base.utils.BeanMapUtils;
import com.mtons.mblog.base.utils.CommonUtils;
import com.mtons.mblog.base.utils.ResourceLock;
import com.mtons.mblog.config.SiteOptions;
import com.mtons.mblog.modules.data.LearnDocVO;
import com.mtons.mblog.modules.data.LearnPlanSetVO;
import com.mtons.mblog.modules.data.UserVO;
import com.mtons.mblog.modules.entity.LearnDoc;
import com.mtons.mblog.modules.entity.LearnDocDetail;
import com.mtons.mblog.modules.entity.LearnPlan;
import com.mtons.mblog.modules.entity.LearnPlanSet;
import com.mtons.mblog.modules.repository.LearnDocDetailRepository;
import com.mtons.mblog.modules.repository.LearnDocRepository;
import com.mtons.mblog.modules.service.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.persistence.criteria.Predicate;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * @author 小神灯的火焰
 */
@Slf4j
@Service
public class LearnDocServiceImpl implements LearnDocService {

    @Resource
    private LearnDocRepository learnDocRepository;
    @Resource
    private UserService userService;
    @Resource
    private LearnDocDetailService learnDocDetailService;
    @Resource
    private LearnDocDetailRepository detailRepository;
    @Resource
    private LearnPlanSetService learnPlanSetService;
    @Resource
    private LearnPlanService learnPlanService;
    @Resource
    private MailService mailService;
    @Autowired
    protected SiteOptions siteOptions;

    public LearnDoc save(LearnDoc obj) {
        return learnDocRepository.save(obj);
    }


    @Transactional
    public List<LearnDoc> saveAll(Iterable<LearnDoc> list) {
        return learnDocRepository.saveAll(list);
    }


    public LearnDoc getOne(String id) {
        return learnDocRepository.getOne(id);
    }


    public LearnDoc findById(String id) {
        Optional<LearnDoc> obj = learnDocRepository.findById(id);
        return obj.orElse(null);
    }


    public void deleteById(String id) {
        learnDocRepository.deleteById(id);
    }


    @Transactional
    public void deleteAll(List list) {
        learnDocRepository.deleteAll(list);
    }


    public void delete(LearnDoc obj) {
        learnDocRepository.delete(obj);
    }


    public boolean existsById(String id) {
        return learnDocRepository.existsById(id);
    }


    public long count() {
        return learnDocRepository.count();
    }


    public List<LearnDoc> findAll() {
        return learnDocRepository.findAll();
    }


    public List<LearnDoc> findAll(LearnDoc obj) {
        List<LearnDoc> list = learnDocRepository.findAll(Example.of(obj));
        return list == null || list.size() < 1 ? null : list;
    }


    public List<LearnDoc> findAll(Sort sort) {
        return learnDocRepository.findAll(sort);
    }


    public List<LearnDoc> findAllById(Iterable<String> ids) {
        return learnDocRepository.findAllById(ids);
    }


    public List<LearnDoc> findAll(Example<LearnDoc> e) {
        return learnDocRepository.findAll(e);
    }


    public List<LearnDoc> findAll(Example<LearnDoc> e, Sort sort) {
        return learnDocRepository.findAll(e, sort);
    }


    public Page<LearnDoc> findAll(Pageable page) {
        return learnDocRepository.findAll(page);
    }


    public Page<LearnDoc> findAll(Example<LearnDoc> e, Pageable page) {
        return learnDocRepository.findAll(e, page);
    }


    public Page<LearnDoc> findAll(LearnDoc obj, Pageable page) {
        return learnDocRepository.findAll(Example.of(obj), page);
    }


    @Override
    public Page<LearnDocVO> paging(Pageable pageable, String contentTypeId, String title) {
        Page<LearnDoc> page = learnDocRepository.findAll((root, query, builder) -> {
            Predicate predicate = builder.conjunction();
            if (StringUtils.isNotEmpty(contentTypeId) && !StringUtils.equals("0",contentTypeId)) {
                predicate.getExpressions().add(
                        builder.equal(root.get("contentTypeId").as(String.class), contentTypeId));
            }
            if (StringUtils.isNotBlank(title)) {
                predicate.getExpressions().add(
                        builder.like(root.get("title").as(String.class), "%" + title + "%"));
            }
            return predicate;
        }, pageable);

        return new PageImpl<>(toLearnDoc(page.getContent()), pageable, page.getTotalElements());
    }

    @Override
    @Transactional
    public void update(LearnDocVO docVO) {
        String id = docVO.getId();
        Optional<LearnDoc> optional = learnDocRepository.findById(id);
        if (optional.isPresent()) {
            String key = ResourceLock.getDocKey(id);
            AtomicInteger lock = ResourceLock.getAtomicInteger(key);
            try {
                synchronized (lock) {
                    final Date nowTime = CommonUtils.getNowTime();
                    LearnDoc learnDoc = optional.get();
                    learnDoc.setTitle(docVO.getTitle());//标题
                    learnDoc.setContentTypeId(docVO.getContentTypeId());
                    learnDoc.setValidTime(docVO.getValidTime());
                    learnDoc.setUpdateTime(nowTime);
                    // 保存文章内容
                    LearnDocDetail docDetail = new LearnDocDetail();
                    docDetail.setDocId(id);
                    final Optional<LearnDocDetail> detailEntity = detailRepository.findOne(Example.of(docDetail));
                    if (detailEntity.isPresent()) {
                        final LearnDocDetail learnDocDetail = detailEntity.get();
                        learnDocDetail.setDocContent(docVO.getContent());
                        learnDocDetail.setUpdateTime(nowTime);
                        detailRepository.save(learnDocDetail);
                    }
                    learnDocRepository.save(learnDoc);
                }
            } finally {
                ResourceLock.giveUpAtomicInteger(key);
            }
        }
    }

    @Override
    public LearnDocVO get(String id) {
        Optional<LearnDoc> po = learnDocRepository.findById(id);
        if (po.isPresent()) {
            LearnDocVO d = BeanMapUtils.copy(po.get());
            d.setAuthor(userService.get(d.getAuthorId()));
            final Optional<LearnDocDetail> docDetail = detailRepository.findOne(((root, query, builder) -> {
                final Predicate conjunction = builder.conjunction();
                if (StringUtils.isNotEmpty(id)) {
                    conjunction.getExpressions().add(
                            builder.equal(root.get("docId").as(String.class), id)
                    );
                }
                return conjunction;
            }));
            if (docDetail.isPresent()) {
                d.setContent(docDetail.get().getDocContent());
                d.setEditor(docDetail.get().getEditorType());
            }
            return d;
        }
        return null;
    }

    @Transactional
    @Override
    public void post(LearnDocVO docVO) {
        final Date nowTime = CommonUtils.getNowTime();
        LearnDoc learnDoc = new LearnDoc();
        final String id = CommonUtils.getUUID();
        learnDoc.setId(id);
        learnDoc.setTitle(docVO.getTitle());//标题
        learnDoc.setContentTypeId(docVO.getContentTypeId());
        learnDoc.setStatus("0");
        learnDoc.setAuthorId(docVO.getAuthorId());
        learnDoc.setCreateTime(nowTime);
        learnDoc.setValidTime(docVO.getValidTime());
        learnDoc.setCreateUser(docVO.getAuthorId());
        // 保存文章内容
        LearnDocDetail docDetail = new LearnDocDetail();
        docDetail.setId(CommonUtils.getUUID());
        docDetail.setDocId(id);
        docDetail.setDocContent(docVO.getContent());
        docDetail.setEditorType(docVO.getEditor());
        docDetail.setCreateTime(nowTime);
        docDetail.setCreateUser(docVO.getAuthorId());
        detailRepository.save(docDetail);
        learnDocRepository.save(learnDoc);
    }

    /**
     * 复习任务，第一次
     *
     * @return
     */
    @Transactional
    @Override
    public int reviewDoc() {
        AtomicInteger count = new AtomicInteger();
        List<LearnDoc> learnDocs = learnDocRepository.queryAllByStatus(Consts.STATUS_0);
        if (CollectionUtils.isNotEmpty(learnDocs)) {
            learnDocs.forEach(doc -> {
                        long authorId = doc.getAuthorId();
                        LearnPlan learnPlan = new LearnPlan();
                        learnPlan.setUserId(authorId);
                        learnPlan.setDocId(doc.getId());
                        List<LearnPlan> learnPlans = learnPlanService.findAll(learnPlan);
                        if (CollectionUtils.isEmpty(learnPlans)) {
                            learnPlan.setFinishStatus(Consts.FINISH_PREPARE);
                            learnPlan.setRemindStatus(Consts.COMMON_NO);
                            List<LearnPlanSetVO> learnPlanSetVOS = learnPlanSetService.queryPlanSettings(authorId);
                            List<LearnPlanSetVO> planSetVOList = learnPlanSetVOS.stream().filter(x -> MemoryType.MEMORY_00.getType().equals(x.getPlanCode())).collect(Collectors.toList());
                            LearnPlanSetVO learnPlanSetVO = planSetVOList.get(0);
                            learnPlan.setPlanSetId(learnPlanSetVO.getId());
                            learnPlan.setDocId(doc.getId());
                            //计算提醒时间
                            long nextMs = MemoryType.MEMORY_00.getSeconds() * 1000;
                            long startMs = doc.getValidTime().getTime();
                            learnPlan.setRemindTime(new Date(nextMs + startMs));
                            learnPlan.setId(CommonUtils.getUUID());
                            learnPlan.setSendEmailStatus(Consts.COMMON_NO);
                            learnPlan.setScore("-1");
                            doc.setStatus(Consts.STATUS_1);
                            learnDocRepository.save(doc);
                            learnPlanService.save(learnPlan);
                            count.getAndIncrement();
                            log.info("文章【{}】复习，第{}次提醒时间为：{}", doc.getTitle(), MemoryType.MEMORY_00.ordinal(), learnPlan.getRemindTime());
                        }
                    }
            );
        }
        return count.get();
    }

    @Transactional
    @Override
    public int sendReviewEmail() {
        LearnPlan learnPlan = new LearnPlan();
        learnPlan.setSendEmailStatus(Consts.COMMON_NO);
        List<LearnPlan> plans = learnPlanService.findAll(learnPlan);
        if (CollectionUtils.isEmpty(plans)) {
            return 0;
        }
        final Date nowTime = CommonUtils.getNowTime();
        AtomicInteger count = new AtomicInteger();
        plans.forEach(plan -> {
            boolean flag = plan.getRemindTime().before(nowTime);
            if (flag) {
                String docId = plan.getDocId();
                Optional<LearnDoc> learnDocOptional = learnDocRepository.findById(docId);
                if (learnDocOptional.isPresent()) {
                    LearnDoc learnDoc = learnDocOptional.get();
                    //生成下一次复习计划
                    //TODO 后续校验，需要验证是否下一次的计划已经生成，防止生成时候出故障
                    int resultCount = learnPlanService.updateEmailStatusById(plan.getId());
                    if (resultCount > 0) {
                        sendPlanEmail(learnDoc, plan);
                        LearnPlanSet nowPlanSet = learnPlanSetService.findById(plan.getPlanSetId());
                        String planCode = nowPlanSet.getPlanCode();
                        MemoryType nowType = MemoryType.getMemoryByType(planCode);
                        if (MemoryType.MEMORY_10 == nowType) {
                            log.info("当前复习的文章：{}，已经是最后一次复习");
                        } else {
                            MemoryType nextMemoryType = MemoryType.getMemoryByOrdinal(nowType.ordinal() + 1);
                            nextPlan(nextMemoryType, learnDoc);
                        }
                        count.getAndIncrement();
                    }
                }

            }
        });
        return count.get();
    }


    private void nextPlan(final MemoryType nextMemoryType, LearnDoc doc) {
        long authorId = doc.getAuthorId();
        LearnPlan learnPlan = new LearnPlan();
        learnPlan.setUserId(authorId);
        learnPlan.setDocId(doc.getId());
        learnPlan.setScore("-1");
        learnPlan.setFinishStatus(Consts.FINISH_PREPARE);
        learnPlan.setRemindStatus(Consts.COMMON_NO);
        //TODO 当前并未针对失效计划进行排除，后续所有信息应该来自plan_set表，只有初始化才会用到枚举类
        List<LearnPlanSetVO> learnPlanSetVOS = learnPlanSetService.queryPlanSettings(authorId);
        List<LearnPlanSetVO> planSetVOList = learnPlanSetVOS.stream().filter(x -> nextMemoryType.getType().equals(x.getPlanCode())).collect(Collectors.toList());
        LearnPlanSetVO learnPlanSetVO = planSetVOList.get(0);
        learnPlan.setPlanSetId(learnPlanSetVO.getId());
        //计算提醒时间
        long nextMs = nextMemoryType.getSeconds() * 1000;
        //TODO 默认以开始时间作为后续的累加时间，不会以完成时间重新计算。后续如果需要可以调整。
        long startMs = doc.getValidTime().getTime();
        learnPlan.setRemindTime(new Date(nextMs + startMs));
        learnPlan.setId(CommonUtils.getUUID());
        learnPlan.setSendEmailStatus(Consts.COMMON_NO);
        learnPlanService.save(learnPlan);
        log.info("文章【{}】复习，第{}次提醒时间为：{}", doc.getTitle(), nextMemoryType.ordinal(), learnPlan.getRemindTime());
    }

    private void sendPlanEmail(LearnDoc doc, LearnPlan plan) {
        UserVO userVO = userService.get(doc.getAuthorId());
        String email = userVO.getEmail();
        String title = MessageFormat.format("您有待复习的内容", siteOptions.getValue("site_name"));
        Map<String, Object> context = new HashMap<>();
        context.put("title", doc.getTitle());
        context.put("learn_url", "http://" + siteOptions.getValue("email_url") + "/learn/doc/show?id=" + plan.getId());
        mailService.sendTemplateEmail(email, title, Consts.EMAIL_TEMPLATE_LEARN, context);
    }

    private List<LearnDocVO> toLearnDoc(List<LearnDoc> learnDocs) {
        List<LearnDocVO> rets = new ArrayList<>();
        HashSet<Long> uids = new HashSet<>();
        HashSet<String> detailIds = new HashSet<>();
        learnDocs.forEach(po -> {
            uids.add(po.getAuthorId());
            rets.add(BeanMapUtils.copy(po));
            detailIds.add(po.getId());
        });

        // 加载用户信息
        buildUsers(rets, uids);
        buildDocDetails(rets, detailIds);
        return rets;
    }

    private void buildUsers(Collection<LearnDocVO> docVOS, Set<Long> uids) {
        Set<Long> uidLongs = new HashSet<>();
        uids.forEach(id -> {
            uidLongs.add(Long.valueOf(id));
        });
        Map<Long, UserVO> userMap = userService.findMapByIds(uidLongs);
        docVOS.forEach(docVO -> docVO.setAuthor(userMap.get(docVO.getAuthorId())));
    }

    private void buildDocDetails(Collection<LearnDocVO> docVOS, Set<String> typeIds) {
        Map<String, LearnDocDetail> map = learnDocDetailService.findMapByIds(typeIds);
        docVOS.forEach(p -> p.setDocDetail(map.get(p.getId())));
    }
}
